package me.sealer.utils;

import javassist.*;
import lombok.extern.slf4j.Slf4j;
import me.sealer.javassist.classloader.CustomClassloader;
import me.sealer.model.asm.ConstructorFrameAndContent;
import me.sealer.model.asm.MethodFrameAndContent;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;

/**
 * @author sealer
 * @date 2018/05/30.
 * @email 1178884049@qq.com
 */
@Slf4j
public class ClassGenerateUtil {
    /**
     * 读取class文件， 转换成对应的Class对象
     *
     * @param sourceFile
     * @return
     */
    public static Class<?> readClassFromFile(File sourceFile) {
        FileInputStream fis = null;
        ByteArrayOutputStream baos = null;
        byte[] result;
        try {
            // 创建的byte数组size
            int size = 1024;
            fis = new FileInputStream(sourceFile);
            baos = new ByteArrayOutputStream(1024);
            byte[] tempBytes = new byte[size];
            // 知道count < size, 即size容得下文件大小
            int count;
            while ((count = fis.read(tempBytes)) != -1) {
                baos.write(tempBytes, 0, count);
            }
            result = baos.toByteArray();
        } catch (IOException e) {
            log.error(e.toString());
            throw new RuntimeException(e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    log.error(e.toString());
                }
            }

            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    log.error(e.toString());
                }
            }
        }

        CustomClassloader customClassloader = new CustomClassloader();
        Class clazz = customClassloader.defineThisClass(result, 0, result.length);
        if (clazz != null && log.isDebugEnabled()) {
            log.debug("Clazz's canonicalName is: " + clazz.getCanonicalName());
        }
        return clazz;
    }

    /**
     * 根据Asm获取class对象
     *
     * @param packageName
     * @param javaClassName
     * @param methods
     * @return
     * @throws CannotCompileException
     */
    public static Class<?> genClassByAsm(String packageName, String javaClassName, List<MethodFrameAndContent> methods, List<ConstructorFrameAndContent> constructors, List<String> fields) throws CannotCompileException {
        CtClass cc = genCtClassByAsm(packageName, javaClassName, methods, constructors, fields);
        return cc.toClass();
    }

    /**
     * 根据Asm获取ctClass对象
     *
     * @param packageName
     * @param javaClassName
     * @param methods
     * @return
     * @throws CannotCompileException
     */
    public static CtClass genCtClassByAsm(String packageName, String javaClassName, List<MethodFrameAndContent> methods, List<ConstructorFrameAndContent> constructors, List<String> fields) {
        ClassPool pool = ClassPool.getDefault();
        // 创建类class
        CtClass cc = pool.makeClass(packageName + "." + javaClassName);

        // 添加域列表
        fields.forEach(f -> {
            try {
                // 定义
                CtField field = CtField.make(f, cc);
                cc.addField(field);
            } catch (CannotCompileException e) {
                log.error(e.toString());
                throw new RuntimeException(e);
            }
        });

        // 添加方法列表
        methods.forEach(m -> {
            try {
                // 定义方法
                CtMethod method = CtNewMethod.make(m.getMethodFrame(), cc);
                // 插入方法代码
                method.insertBefore(m.getMethodContent());
                cc.addMethod(method);
            } catch (Exception e) {
                log.error(e.toString());
                throw new RuntimeException(e);
            }
        });

        // 添加构造器列表
        constructors.forEach(c -> {
            try {
                // 定义构造器
                CtConstructor constructor = CtNewConstructor.make(c.getConstructorFrame(), cc);
                // 插入构造器代码
                constructor.insertAfter(c.getConstructorContent());
                cc.addConstructor(constructor);
            } catch (Exception e) {
                log.error(e.toString());
                throw new RuntimeException(e);
            }
        });
        return cc;
    }
}
